package utils

import org.apache.batik.transcoder.image.PNGTranscoder
import org.apache.batik.transcoder.{TranscoderInput, TranscoderOutput}
import org.apache.commons.io.FileUtils
import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation
import org.apache.pdfbox.pdmodel.PDDocument
import org.apache.pdfbox.rendering.PDFRenderer
import org.joda.time.DateTime
import play.api.libs.json.Json
import play.api.mvc.Result
import shapeless._
import shapeless.ops.hlist._
import shapeless.ops.record._
import shapeless.record._
import tool.ConvertHelper

import java.io.{File, FileInputStream, FileOutputStream}
import java.util.zip.GZIPOutputStream
import javax.imageio.ImageIO
//import org.apache.commons.math3.stat.StatUtils
//import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation
//import org.saddle.io._
//import CsvImplicits._
//import javax.imageio.ImageIO
//import org.apache.pdfbox.pdmodel.PDDocument
//import org.apache.pdfbox.rendering.PDFRenderer

import implicits.Implicits._

import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future}

object Utils {

  def isWindows = {
    System.getProperty("os.name") match {
      case x if x.contains("Windows") => true
      case _ => false
    }
  }

  val Rscript = {
    "Rscript"
  }

  val phylotreeCss =
    """
      |<style>
      |.tree-selection-brush .extent {
      |    fill-opacity: .05;
      |    stroke: #fff;
      |    shape-rendering: crispEdges;
      |}
      |
      |.tree-scale-bar text {
      |  font: sans-serif;
      |}
      |
      |.tree-scale-bar line,
      |.tree-scale-bar path {
      |  fill: none;
      |  stroke: #000;
      |  shape-rendering: crispEdges;
      |}
      |
      |.node circle, .node ellipse, .node rect {
      |fill: steelblue;
      |stroke: black;
      |stroke-width: 0.5px;
      |}
      |
      |.internal-node circle, .internal-node ellipse, .internal-node rect{
      |fill: #CCC;
      |stroke: black;
      |stroke-width: 0.5px;
      |}
      |
      |.node {
      |font: 10px sans-serif;
      |}
      |
      |.node-selected {
      |fill: #f00 !important;
      |}
      |
      |.node-collapsed circle, .node-collapsed ellipse, .node-collapsed rect{
      |fill: black !important;
      |}
      |
      |.node-tagged {
      |fill: #00f;
      |}
      |
      |.branch {
      |fill: none;
      |stroke: #999;
      |stroke-width: 2px;
      |}
      |
      |.clade {
      |fill: #1f77b4;
      |stroke: #444;
      |stroke-width: 2px;
      |opacity: 0.5;
      |}
      |
      |.branch-selected {
      |stroke: #f00 !important;
      |stroke-width: 3px;
      |}
      |
      |.branch-tagged {
      |stroke: #00f;
      |stroke-dasharray: 10,5;
      |stroke-width: 2px;
      |}
      |
      |.branch-tracer {
      |stroke: #bbb;
      |stroke-dasharray: 3,4;
      |stroke-width: 1px;
      |}
      |
      |
      |.branch-multiple {
      |stroke-dasharray: 5, 5, 1, 5;
      |stroke-width: 3px;
      |}
      |
      |.branch:hover {
      |stroke-width: 10px;
      |}
      |
      |.internal-node circle:hover, .internal-node ellipse:hover, .internal-node rect:hover {
      |fill: black;
      |stroke: #CCC;
      |}
      |
      |.tree-widget {
      |}
      |</style>
    """.stripMargin

  def deleteDirectory(direcotry: File) = {
    try {
      FileUtils.deleteDirectory(direcotry)
    } catch {
      case _ =>
    }
  }

  def getTime(startTime: Long) = {
    val endTime = System.currentTimeMillis()
    (endTime - startTime) / 1000.0
  }

  def logTime(startTime: Long) = {
    val time = getTime(startTime)
    println(time)
  }

  def logTime(beforeStr: String, startTime: Long) = {
    val time = getTime(startTime)
    println(beforeStr, time)
  }

  def spentTime(f: () => Unit) = {
    val startTime = System.currentTimeMillis()
    f()
    val time = getTime(startTime)
    println(time)
  }

  def execFuture[T](f: Future[T]): T = {
    Await.result(f, Duration.Inf)
  }

  def getValue[T](kind: T, noneMessage: String = ""): String = {
    kind match {
      case x if x.isInstanceOf[DateTime] =>
        val time = x.asInstanceOf[DateTime]
        time.toString("yyyy-MM-dd")
      case x if x.isInstanceOf[Option[T]] =>
        val option = x.asInstanceOf[Option[T]]
        if (option.isDefined) getValue(option.get, noneMessage) else noneMessage
      case x if x.isInstanceOf[Seq[T]] =>
        val list = x.asInstanceOf[Seq[T]]
        list.mkString(";")
      case _ => kind.toString
    }
  }

  def getArrayByTs[T, R <: HList](
      x: Seq[T]
  )(implicit gen: LabelledGeneric.Aux[T, R], toMap: ToMap.Aux[R, Symbol, Any]) = {
    x.map { y =>
      gen.to(y).toMap.map { case (symbol, value) =>
        (symbol.name, getValue(value))
      }
    }
  }

  def getArrayByTs[T, R <: HList](x: Seq[T], jsonField: String)(implicit
      gen: LabelledGeneric.Aux[T, R],
      toMap: ToMap.Aux[R, Symbol, Any]
  ) = {
    x.map { y =>
      gen.to(y).toMap.flatMap { case (symbol, value) =>
        val fieldName = symbol.name
        val tmpValue = getValue(value)
        val finalMap = if (symbol.name == jsonField) {
          Json.parse(tmpValue).as[Map[String, String]]
        } else Map(fieldName -> tmpValue)
        finalMap
      }
    }
  }

  def getLinesByTs[T, R <: HList, K <: HList, V <: HList](ys: List[T])(implicit
      gen: LabelledGeneric.Aux[T, R],
      keys: Keys.Aux[R, K],
      values: Values.Aux[R, V],
      ktl: ToList[K, Symbol],
      vtl: ToList[V, Any]
  ) = {
    val fieldNames = keys().toList.map(_.name)
    val lines = ys.map { y =>
      gen.to(y).values.toList.map(_.toString)
    }
    fieldNames :: lines
  }

  def pdf2png(tmpDir: File, fileName: String) = {
    val pdfFile = new File(tmpDir, fileName)
    val outFileName = fileName.substring(0, fileName.lastIndexOf(".")) + ".png"
    val outFile = new File(tmpDir, outFileName)
    val document = PDDocument.load(pdfFile)
    val renderer = new PDFRenderer(document)
    ImageIO.write(renderer.renderImage(0, 3), "png", outFile)
    document.close()
  }

  def to[A]: ConvertHelper[A] = {
    new ConvertHelper[A]
  }

  def svg2png(svgFile: File, pngFile: File) = {
    val input = new TranscoderInput(svgFile.toURI.toString)
    val outStream = new FileOutputStream(pngFile)
    val output = new TranscoderOutput(outStream)
    val t = new PNGTranscoder()
    t.transcode(input, output)
    outStream.flush()
    outStream.close()
  }

  def sd(x: List[Double]): Double = {
    val sd = new StandardDeviation()
    sd.evaluate(x.toArray)
  }

  def mean(x: List[Double]) = {
    x.sum / x.size
  }

  def cv(x: List[Double]) = {
    val sd = Utils.sd(x)
    val meanValue = Utils.mean(x)
    sd / meanValue
  }

  def createAndReturnDir(dir: File, subDirName: String) = {
    new File(dir, subDirName).createDirectoryWhenNoExist
  }

  def getTxtFileJson(file: File) = {
    Json.toJson(file.txtLines.lineMap)
  }

  def getTxtFileJsonNoLower(file: File) = {
    Json.toJson(file.txtLines.lineMapNoLower)
  }

  def getJsonByT[T, R <: HList](y: T)(implicit gen: LabelledGeneric.Aux[T, R], toMap: ToMap.Aux[R, Symbol, Any]) = {
    val map = gen.to(y).toMap.map { case (symbol, value) =>
      (symbol.name, getValue(value))
    }
    Json.toJson(map)
  }

  def getInfoByLines(lines: List[List[String]]) = {
    val columnNames = lines.head
    val cNames = lines.head.zipWithIndex.map { case (v, i) =>
      s"c${i}"
    }
    val map = cNames.zip(columnNames).toSeqMap
    val array = lines.drop(1).map { tmpColumns =>
      val columns = tmpColumns.map(_.trimQuote).padTo(cNames.size, "")
      cNames.zip(columns).toMap
    }
    (map, array)
  }

  def getJsonByT[T, R <: HList](y: T, jsonField: String)(implicit
      gen: LabelledGeneric.Aux[T, R],
      toMap: ToMap.Aux[R, Symbol, Any]
  ) = {
    val json = gen
      .to(y)
      .toMap
      .map { case (symbol, value) =>
        val fieldName = symbol.name
        val tmpValue = getValue(value)
        val finalJson = if (symbol.name == jsonField) {
          Json.parse(tmpValue).toJsObject
        } else Json.obj(fieldName -> tmpValue)
        finalJson
      }
      .toList
      .reduceLeft { (x, y) => x ++ y }
    json
  }

  def getJsonByTs[T, Repr <: HList](
      x: Seq[T]
  )(implicit gen: LabelledGeneric.Aux[T, Repr], toMap: ToMap.Aux[Repr, Symbol, Any]) = {
    val array = getArrayByTs(x)
    Json.toJson(array)
  }

  def result2Future(rs: Result) = {
    Future.successful(rs)
  }

  def getRelativePath(parent: File, file: File) = {
    val parentPath = parent.getAbsolutePath.slashL2R
    file.getAbsolutePath.slashL2R.replaceAll(s"^" + parentPath + "/", "")
  }

  def gZip(file: File, gzFile: File) = {
    val fin = new FileInputStream(file)
    val fout = new FileOutputStream(gzFile)
    val gzout = new GZIPOutputStream(fout)

    def loop: Unit = {
      val buf = new Array[Byte](1024)
      val num = fin.read(buf)
      num match {
        case x if x == -1 =>
        case _ =>
          gzout.write(buf, 0, num)
          loop
      }
    }

    loop
    gzout.close()
    fout.close()
    fin.close()
  }

  def isLike(v: String, dbV: String) = {
    if (v.isEmpty) true else dbV.contains(v)
  }

}
